/*
 * ============================================================================
 *                   GNU Lesser General Public License
 * ============================================================================
 *
 *
 *
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 * 
 *
 *
 */
package rad.framework.security;

import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.ejb.SessionContext;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.jacc.PolicyContext;

import org.jboss.seam.Component;
import org.jboss.seam.security.Identity;
import org.jboss.security.SecurityAssociation;

/**
 * This is a utility class that provide convenience methods for working with the
 * JAAS API. Feel free to use these or not. It currently has dependencies on
 * jboss.
 * 
 * @author
 * @version 1.0
 * 
 */
public class JAASUtil {

	/**
	 * Get the current subject
	 */
	static public Subject getSubject() {
		Subject subject = null;
		if (getIdentity() != null) {
			subject = getIdentity().getSubject();
		}
		if (subject == null) {
			try {
				subject = (Subject) PolicyContext
						.getContext("javax.security.auth.Subject.container");
			} catch (Exception e) {
				// e.printStackTrace();
			}
		}
		if (subject == null) {
			// TODO need to handle lookup with credentials in unit tests to remove this dependency
			subject = SecurityAssociation.getSubject();
		}
		return subject;
	}

	/**
	 * Set the current subject
	 */
	static public void setSubject(Subject s) {
		SecurityAssociation.setSubject(s);
	}

	/**
	 * Set the current principal
	 */
	static public void setPrincipal(Principal p) {
		SecurityAssociation.setPrincipal(p);
	}

	/**
	 * Get the current principal.
	 */
	static public Principal getPrincipal() {
		Principal p = null;
		if (getIdentity() != null) {
			p = getIdentity().getPrincipal();
		}
		if (p == null) {
			p = SecurityAssociation.getPrincipal();
		}
		return p;
	}

	static public String getCurrentUserName() {
		Principal p = null;
		if (getIdentity() != null) {
			p = getIdentity().getPrincipal();
		}
		if (p == null) {
			p = SecurityAssociation.getPrincipal();
		}
		if (p == null) {
			return null;
		}
		return p.getName();
	}
	
	/**
	 * Get the caller from the session context.
	 */
	static public Principal getCallerPrincipal(SessionContext sessionCtx) {
		Principal currentUser = null;
		try {
			currentUser = sessionCtx.getCallerPrincipal();
		} catch (IllegalStateException e) {
			if (getIdentity() != null) {
				currentUser = getIdentity().getPrincipal();
			}
		}
		if (currentUser == null) {
			throw new RuntimeException("Not logged in.");
		}
		return currentUser;
	}

	protected static Identity getIdentity() {
		// related to http://jira.jboss.org/jira/browse/JBSEAM-729
		try {
			return (Identity) Component.getInstance("org.jboss.seam.security.identity");
		} catch (IllegalStateException e) {
			return null;
		}
	}

	/**
	 * Gets roles from the current subject.
	 */
	static public List<Principal> getRoles() {
		return getRoles(getSubject());
	}

	static public List<String> getRolesAsString() {
		List<String> roles = new ArrayList<String>();
		for (Principal principal : getRoles()) {
			roles.add(principal.getName());
		}
		return roles;
	}
	
	/**
	 * Gets roles from the specified subject.
	 */
	static public List<Principal> getRoles(Subject s) {
		List<Principal> principals = new ArrayList<Principal>();
		if (s != null) {
			Set set = s.getPrincipals(Group.class);
			if (set != null) {
				Iterator i = set.iterator();
				if (i.hasNext()) {
					Group g = (Group) i.next();
					Enumeration j = g.members();
					while (j.hasMoreElements()) {
						Principal p = (Principal) j.nextElement();
						principals.add(p);
					}
				}
			}
		}
		return principals;
	}

	/**
	 * @param module
	 * @param user
	 * @param credential
	 * @throws LoginException
	 */
	static public void login(String module, String user, Object credential)
			throws LoginException {
		CallbackHandler handler = new UserIdPasswordCallbackHandler(user,
				credential);
		login(module, user, credential, handler);
	}

	/**
	 * Invoke JAAS to login
	 * 
	 * @param module -
	 *            which jaas setup
	 * @param userid
	 * @param credential
	 * @param handler
	 * @return
	 * @throws LoginException
	 */
	static public LoginContext login(String module, String userid,
			Object credential, CallbackHandler handler) throws LoginException {
		LoginContext ctx = new LoginContext(module, handler);
		ctx.login();
		return ctx;
	}

	/**
	 * Simple handler used in unit testing.
	 */
	static class UserIdPasswordCallbackHandler implements CallbackHandler {
		private String user;

		private Object credential;

		/**
		 * @param user
		 * @param credential
		 */
		public UserIdPasswordCallbackHandler(String user, Object credential) {
			this.user = user;
			this.credential = credential;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
		 */
		public void handle(Callback[] callbacks) throws IOException,
				UnsupportedCallbackException {
			for (int i = 0; i < callbacks.length; i++) {
				if (callbacks[i] instanceof NameCallback) {
					NameCallback nc = (NameCallback) callbacks[i];
					nc.setName(user);
				} else if (callbacks[i] instanceof PasswordCallback) {
					PasswordCallback pc = (PasswordCallback) callbacks[i];
					pc.setPassword(credential.toString().toCharArray());
				} else {
					throw new UnsupportedCallbackException(callbacks[i],
							"Unrecognized Callback");
				}
			}
		}
	}

	/**
	 * Used in JUnit tests to initialize jaas
	 * 
	 * @param location
	 */
	static public void setConfigFile(String location) {
		System.setProperty("java.security.auth.login.config", location);
	}
}
